package infolayer2.mql;

import infolayer2.graph.Context;
import infolayer2.graph.Edge;
import infolayer2.graph.EdgeSet;
import infolayer2.graph.Node;
import infolayer2.json.Json;

import java.util.Iterator;
import java.util.List;

import com.google.appengine.labs.repackaged.com.googlecode.charts4j.collect.Lists;


public class MqlProcessor {
  
  private final Context context;
  
  public MqlProcessor(Context context) {
    this.context = context;
  }

  public static String getQualifiedPropertyName(String key, Node type) {
    if (key.startsWith("/")) {
      return key;
    }
    
    if (type != null) {
      String check = "/" + key;
      for (Edge property : type.get("/type/type/properties")) {
        if (property.getTo().endsWith(check)) {
          return property.getTo();
        }
      }
    }
    
    return "/type/object/" + key;
  }
  
  public static boolean matches(Node entity, Json filter, Node type) {
    for (String key: filter.getKeys()) {
      String propertyName = getQualifiedPropertyName(key, type);
      switch(filter.getType(key)) {
      case BOOLEAN:
      case NUMBER:
      case STRING:
        if (!entity.get(propertyName).contains(filter.getObject(key))) {
          return false;
        }
        break;
      case NULL:
        break;
      case OBJECT:
        // TODO
        break;
      }
    }
    
    return true;
  }
  
  public Json get(Node entity, Json template, Node type) {
    Json result = Json.createObject();
    for (String key: template.getKeys()) {
      // TODO(haustein) improve getQualifiedPropertyName
      String propertyName = getQualifiedPropertyName(key, type);
      switch(template.getType(key)) {
      case BOOLEAN:
      case NUMBER:
      case STRING:
        result.setObject(key, template.getObject(key));
        break;
      case NULL: {
        EdgeSet values = entity.get(propertyName);
        Iterator<Edge> i = values.iterator();
        if (i.hasNext()) {
          Edge value = i.next();
          if (value.hasValue()) {
            result.setObject(key, value.getValue());
          } else {
            // TODO: Name or id here?
            result.setString(key, "" + context.getGraph().get(value.getTo()));
          }
        }
        break;
      }
      case OBJECT: {
        List<Node> nodes = Lists.newArrayList();
        for(Edge e: entity.get(propertyName)) {
          String target = e.getTo();
          if (e != null) {
            Node node = context.getGraph().get(target);
            if (target != null) {
              nodes.add(node);
            }
          }
        }
        result.setJson(key, read(nodes, template.getJson(key),
            context.getGraph().getExpectedType(propertyName)));
        break;
      }
      }
    }
    return result;
  }
  
  public Json read(Json query) {
    return read(context.getGraph(), query, null);
  }
  
  
  
  private Json read(Iterable<Node> entities, Json query, Node type) {
    return query.isArray() 
        ? readArray(entities, query, type)
        : readOne(entities, query, type);
  }
  
  private Node getType(Json query) {
    String id = query.getString("type");
    if (id == null) {
      id = query.getString("/type/object/type");
    }
    return id == null ? null : context.getGraph().get(id);
  }
  
  private Json readArray(Iterable<Node> entities, Json query, Node type) {
    Json result = Json.createArray();
    Json filter = query.getJson(0);

    if (type == null) {
      type = getType(filter);
    }
    for (Node entity: entities) {
      if (matches(entity, filter, type)) {
        result.setJson(result.length(), get(entity, filter, type));
      }
    }
    return result;
  }
  
  private Json readOne(Iterable<Node> entities, Json query, Node type) {
    if (type == null) {
      type = getType(query);
    }
    
    for (Node entity: entities) {
      if (matches(entity, query, type)) {
        return get(entity, query, type);
      }
    }
    return Json.createObject();
  }

  private Json set(Node target, Json values, Node type) {
    for (String key: values.getKeys()) {
      if (values.getType(key) == Json.Type.OBJECT) {
        Json value = values.getJson(key);
        String propertyName = getQualifiedPropertyName(key, type);
        target.setValue(propertyName, value.getString("value"));
      }
    }
    return values;
  }
  
  public Json write(Json value) {
    Node type = getType(value);
    
    for (Node candidate: context.getGraph()) {
      if (matches(candidate, value, type)) {
        return set(candidate, value, type);
      }
    }
    
    
    return Json.parse("{}");
  }

  
    
}
